#ifndef VECTOR3_H
#define VECTOR3_H

#include <cmath>

class Vector3
{
public:

    float x;
    float y;
    float z;

	Vector3() : x(0.f), y(0.f), z(0.f) {}
    Vector3( float _x, float _y, float _z ){ x = _x; y = _y; z = _z; }

	inline void set( float _x, float _y, float _z ){ x = _x; y = _y; z = _z; }

    // functions 
	inline float length() const { return( (float)sqrt( x * x + y * y + z * z ) ); }
	inline float lengthSquared() const { return( x * x + y * y + z * z ); }
    inline void normalize()
	{
		float l = length();

		x = x / l;
		y = y / l;
		z = z / l;
	}

	inline Vector3 normal( ) const
	{
		float l = length();
		if( l != 1.f )
			return Vector3( x/l, y/l, z/l );
		else
			return *this;
	}

	inline Vector3 lerp( const Vector3 & targetLocation, float s ) const
	{
		return Vector3( x + s * ( targetLocation.x - x ),
			y + s * ( targetLocation.y - y ),
			z + s * ( targetLocation.z - z ) );
	}

	inline float dot( const Vector3 & other ) const
	{
		return (x * other.x) + (y * other.y) + (z * other.z);
	}

	inline Vector3 cross( const Vector3 & other ) const
	{
		return Vector3((y * other.z) - (z * other.y),
			(z * other.x) - (x * other.z),
			(x * other.y) - (y * other.x));
	}
	inline void crossWith( const Vector3 & other )
	{
		float X = (y * other.z) - (z * other.y);
		float Y = (z * other.x) - (x * other.z);
		this->z = (x * other.y) - (y * other.x);
		this->x = X;
		this->y = Y;
		
	}
	inline Vector3 rotate( float angle, Vector3 axis )
	{
		if( angle == 0 )
			return *this;
		float cosAl = 1-cos(angle);
		float sinAl = sin(angle);

		axis.normalize();

		Vector3 v = *this;

		// put the rotated vector into rotated
		x = (v.x * ( 1 + cosAl*(axis.x*axis.x-1) ) ) +
					(v.y * ( axis.z*sinAl+cosAl*axis.x*axis.y ) ) +
					(v.z * ( -axis.y*sinAl+cosAl*axis.x*axis.z ) );
		y = (v.x * ( -axis.z*sinAl+cosAl*axis.x*axis.y ) ) +
					(v.y * ( 1 + cosAl*(axis.y*axis.y-1) ) ) +
					(v.z * ( axis.x*sinAl+cosAl*axis.y*axis.z ) );
		z = (v.x * ( axis.y*sinAl+cosAl*axis.x*axis.z ) ) +
					(v.y * ( -axis.x*sinAl+cosAl*axis.y*axis.z ) ) +
					(v.z * ( 1 + cosAl*(axis.z*axis.z-1) ) );


		return *this;
	}
	inline Vector3 rotate( float _x, float _y, float _z )
	{
		// NOTE cos 90 degrees cannot be evaluated, gives something like 9.0859e93 ( not 9.0..... ) is more like 0.0000001
		float pio180 = 3.141592653f / 180.f;
		_x = _x * pio180;
		_y = _y * pio180;
		_z = _z * pio180;
		float cx = cos(_x);
		float sx = sin(_x);
		float cy = cos(_y);
		float sy = sin(_y);
		float cz = cos(_z);
		float sz = sin(_z);

		Vector3 v = *this;

		x = ( v.x*(cy*cz) ) + ( v.y*(((sx*sy)*cz)+(cx*-sz)) ) + (v.z*(((cx*sy)*cz)+(-sx*-sz)));
		y = ( v.x*(cy*sz) ) + ( v.y*(((sx*sy)*sz)+(cx*cz)) ) + (v.z*(((cx*sy)*sz)+(-sx*cz)) );
		z = ( v.x*(-sy) ) + ( v.y*(sx*cy) ) + (v.z*(cx*cy) );

		return *this;
	}


	// operators
	inline Vector3 & operator = ( const Vector3 & other )
	{
		x = other.x;
		y = other.y;
		z = other.z;
		return *this;
	}
    inline Vector3 & operator += ( const Vector3 &other )
	{
		x += other.x;
		y += other.y;
		z += other.z;
		return *this;
	}
    inline Vector3 & operator -= ( const Vector3 & other )
	{
		x -= other.x;
		y -= other.y;
		z -= other.z;
		return *this;
	}
	inline Vector3 & operator *= ( const Vector3 & other )
	{
		x *= other.x;
		y *= other.y;
		z *= other.z;
		return *this;
	}
	inline Vector3 & operator /= ( const Vector3 & other )
	{
		x /= other.x;
		y /= other.y;
		z /= other.z;
		return *this;
	}

	inline Vector3 & operator += ( float scalar )
	{
		x += scalar;
		y += scalar;
		z += scalar;
		return *this;
	}
	inline Vector3 & operator -= ( float scalar )
	{
		x -= scalar;
		y -= scalar;
		z -= scalar;
		return *this;
	}
    inline Vector3 & operator *= ( float scalar )
	{
		x *= scalar;
		y *= scalar;
		z *= scalar;
		return *this;
	}
    inline Vector3 & operator /= ( float scalar )
	{
		x /= scalar;
		y /= scalar;
		z /= scalar;
		return *this;
	}

	// operators against other Vector3
    inline Vector3 operator + ( const Vector3 & other ) const
	{
		return Vector3( x+other.x, y+other.y, z+other.z );
	}
	inline Vector3 operator - ( const Vector3 & other ) const
	{
		return Vector3( x-other.x, y-other.y, z-other.z );
	}
    inline Vector3 operator * ( const Vector3 & other ) const
	{
		return Vector3( x*other.x, y*other.y, z*other.z );
	}
    inline Vector3 operator / ( const Vector3 & other ) const
	{
		return Vector3( x/other.x, y/other.y, z/other.z );
	}

	// operators against scalars
	inline Vector3 operator + ( float scalar ) const
	{
		return Vector3( x+scalar, y+scalar, z+scalar );
	}
	inline Vector3 operator - ( float scalar ) const
	{
		return Vector3( x-scalar, y-scalar, z-scalar );
	}
    inline Vector3 operator * ( float scalar ) const
	{
		return Vector3( x*scalar, y*scalar, z*scalar );
	}
    inline Vector3 operator / ( float scalar ) const
	{
		return Vector3( x/scalar, y/scalar, z/scalar );
	}

	friend inline Vector3 operator + ( const float scalar, const Vector3 & v )
	{
		return Vector3( v.x+scalar, v.y+scalar, v.z+scalar );
	}
	friend inline Vector3 operator - ( const float scalar, const Vector3 & v )
	{
		return Vector3( v.x-scalar, v.y-scalar, v.z-scalar );
	}
	friend inline Vector3 operator * ( const float scalar, const Vector3 & v )
	{
		return Vector3( v.x*scalar, v.y*scalar, v.z*scalar );
	}
	friend inline Vector3 operator / ( const float scalar, const Vector3 & v )
	{
		return Vector3( v.x/scalar, v.y/scalar, v.z/scalar );
	}
	


	// self changes Vector3 b = -a;
    inline Vector3 operator + () const { return *this; }
	inline Vector3 operator - () const { return Vector3(-x, -y, -z); }

};


#endif